package kassiopeia.agent.plugin;

import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import kassiopeia.agent.repository.RepositoryManager;
import kassiopeia.commons.exception.NoDescriptorException;
import kassiopeia.commons.exception.PluginCreationException;
import kassiopeia.commons.model.Coordinates;
import kassiopeia.commons.model.plugin.PluginDescriptor;
import kassiopeia.commons.model.xml.PluginDescriptorXmlSerializer;
import kassiopeia.plugin.Plugin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class PluginFactory {

    @Autowired
    private ContextFactory ctxFactory;
    @Autowired
    private RepositoryManager repositoryManager;
    private Map<Coordinates, PluginDescriptor> descriptors;
    private Map<Coordinates, ClassLoader> classLoaders;

    public PluginFactory() {
        descriptors = new HashMap<Coordinates, PluginDescriptor>();
        classLoaders = new HashMap<Coordinates, ClassLoader>();
    }

    public Plugin newInstance(Coordinates coordinates, String instanceId) {
        try {
            return tryNewInstance(coordinates, instanceId);
        } catch (Throwable throwable) {
            throw new PluginCreationException("Can't create plugin instance: " + instanceId, throwable);
        }
    }

    private Plugin tryNewInstance(Coordinates coordinates, String instanceId) throws Exception {
        PluginDescriptor descriptor = descriptors.get(coordinates);
        ClassLoader loader = classLoaders.get(coordinates);

        if (descriptor == null) {
            File pluginJar = repositoryManager.getPluginJar(coordinates);
            loader = new PluginClassLoader(pluginJar);
            descriptor = retrieveDescriptor(pluginJar, loader);

            classLoaders.put(coordinates, loader);
            descriptors.put(coordinates, descriptor);
        }

        Plugin plugin = createPluginInstance(loader, descriptor.getPluginClass());
        plugin.setContext(ctxFactory.newInstance(descriptor, loader, instanceId));

        return plugin;
    }

    private Plugin createPluginInstance(ClassLoader loader, String pluginClassName) throws Exception {
        ClassLoader _classLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(loader);
        try {
            return (Plugin) loader.loadClass(pluginClassName).newInstance();
        } finally {
            Thread.currentThread().setContextClassLoader(_classLoader);
        }
    }

    private PluginDescriptor retrieveDescriptor(File pluginJar, ClassLoader loader) {
        InputStream stream = loader.getResourceAsStream(Plugin.PLUGIN_DESCRIPTOR_PATH);
        if (stream == null) {
            throw new NoDescriptorException(pluginJar.getName());
        }
        return PluginDescriptorXmlSerializer.fromXml(stream);
    }
}
